#!/usr/bin/env python  
# -*- coding: utf-8 -*-  

#
#   Very simple python FUSE memory file system
#   this one is making blocks of the file data
#   so we dont store the same data 2 times
#
#
#   Mostly Complete, Working
#   ToDo: filesystem stats, keep file/dir metadata
#





import errno  
import fuse  
import stat  
import time  
import os
import zlib
import hashlib, math
from collections import defaultdict
  
fuse.fuse_python_api = (0, 2)  
  
class MyFS(fuse.Fuse):  
    def __init__(self, *args, **kw):  
        fuse.Fuse.__init__(self, *args, **kw)  
        self.stuff=dict()
        self.hashes=dict()
        self.hashCounts=dict()
        self.addDir("/")
        self.bs = 1024*4
        self.fd=0
        self.log_level=4

    def ckHashes(self):
        save = 0
        for s in self.hashCounts:
            if self.hashCounts[s] > 1:
                save = save + self.hashCounts[s]
        print "Total Saved %d"%(save*self.bs)
            

    def addFile(self, path):
        self.stuff[path]=dict() 
        self.stuff[path]['st']=fuse.Stat()
        self.stuff[path]['st']=fuse.Stat()
        self.stuff[path]['st'].st_mode = stat.S_IFREG | 0777
        self.stuff[path]['st'].st_nlink = 1
        self.stuff[path]['st'].st_atime = int(time.time())
        self.stuff[path]['st'].st_mtime = int(time.time())
        self.stuff[path]['st'].st_ctime = int(time.time())
        self.stuff[path]['st'].st_size = 0
        self.stuff[path]['data']=list()


    def addDir(self, path):
        self.stuff[path]=dict() 
        self.stuff[path]['st']=fuse.Stat()
        self.stuff[path]['st'].st_mode = stat.S_IFDIR | 0777
        self.stuff[path]['st'].st_nlink = 2
        self.stuff[path]['st'].st_atime = int(time.time())
        self.stuff[path]['st'].st_mtime = int(time.time())
        self.stuff[path]['st'].st_ctime = int(time.time())

         

    def getattr(self, path):  
        if path in self.stuff:
            return self.stuff[path]['st']
        return -errno.ENOENT

    def readdir(self, path, offset):  
        send=['.','..']
        for i in self.stuff:
            if i[:len(path)].count(path) > 0:
                lenPath= len(path)
                if path == "/":
                    lenPath=0
                if i[lenPath:lenPath+1] == "/" and i[lenPath+1:].count("/") <=0 and len(i[lenPath+1:])>0:
                    send.append(i[lenPath+1:])
        for x in send:
            yield fuse.Direntry(x)

    def mkdir(self, path, mode):
        return self.addDir(path)

    def rmdir(self, path):
        tmp = list()
        for i in self.stuff:
            tmp.append(i[:len(path)])
        if i.count(path) > 1:
            return -errno.ENOTEMPTY
        del self.stuff[path]

    def unlink(self, path):
        self.ckHashes()
        if path in self.stuff:
            for f in self.stuff[path]['data']:
                if type(f) is str and (f in self.hashCounts):
                    if self.hashCounts[f] == 1:
                        del self.hashCounts[f]
                        self.hashes[f]=""
                        del self.hashes[f]
                    else:
                        self.hashCounts[f] = self.hashCounts[f]-1
            del self.stuff[path]
        print "Blocks: %d"%len(self.hashes)
        
    def mknod(self, path, mode, dev):
        rt = self.addFile(path)
        return 0

    def open(self, path, flags):
        return 0

    def utimens(self, path, tmp, tmp2):
        return 0

    def read(self, path, size, offset):
        if path in self.stuff:
            start = int(math.floor(offset/self.bs))
            end = int(math.ceil((offset+size)/self.bs))
            data = bytearray()
            for i in range(start, end):
                if len(self.stuff[path]['data'])-1 >= i:
                    if type(self.stuff[path]['data'][i]) is str:
                        data.extend(self.hashes[self.stuff[path]['data'][i]])
                    else:   
                        data.extend(self.stuff[path]['data'][i])
            offset = offset%self.bs
            return str(data[offset:size])
        return -errno.ENOENT

    def write(self, path, data, offset):
        ret = len(data)
        if path in self.stuff:
            size = len(self.stuff[path]['data'])
            extra = 0
            if size > 0:
                size = size-1
                if type(self.stuff[path]['data'][len(self.stuff[path]['data'])-1]) is str:
                    extra = self.bs
                else:
                    extra = len(self.stuff[path]['data'][len(self.stuff[path]['data'])-1])
            size = (size * self.bs) + extra
            if size == offset:
                if extra > 0 and extra < self.bs:
                    last = len(self.stuff[path]['data'])-1
                    data = str(self.stuff[path]['data'][last]) + data
                    del self.stuff[path]['data'][last]
                while len(data) > 0:
                    if len(data) >= self.bs:
                        md5 = hashlib.md5(data[:self.bs]).hexdigest()
                        if md5 in self.hashes:
                            self.stuff[path]['data'].append(md5)
                            self.hashCounts[md5] = self.hashCounts[md5]+1
                        else:
                            self.stuff[path]['data'].append(md5)
                            self.hashes[md5]=data[:self.bs]
                            self.hashCounts[md5]=1
                        data = data[self.bs:]
                    else:
                        self.stuff[path]['data'].append(bytearray(data))
                        data = ""
            else:
                #Can not write to file if you are not appending to the end of it
                #This type of FS for archiving only, delete and readd the new file
                return -errno.EPERM
            self.stuff[path]['st'].st_size = self.stuff[path]['st'].st_size + ret
#        self.ckHashes()
#        print "Blocks: %d"%len(self.hashes)
        return ret

    def truncate(self, path, length):
        return -errno.EPERM
        
        
        
        
      
if __name__ == '__main__':  
    fs = MyFS()  
    fs.parse(errex=1)  
    fs.main()  
